/*******************************************************************************
*Copyright (c) 2009 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
*
* This file is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Please contact Eucalyptus Systems, Inc., 130 Castilian
* Dr., Goleta, CA 93101 USA or visit <http://www.eucalyptus.com/licenses/>
* if you need additional information or have any questions.
*
* This file may incorporate work covered under the following copyright and
* permission notice:
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2008, Regents of the University of California
* All rights reserved.
*
* Redistribution and use of this software in source and binary forms, with
* or without modification, are permitted provided that the following
* conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. USERS OF
* THIS SOFTWARE ACKNOWLEDGE THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE
* LICENSED MATERIAL, COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS
* SOFTWARE, AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
* IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, SANTA
* BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, WHICH IN
* THE REGENTS’ DISCRETION MAY INCLUDE, WITHOUT LIMITATION, REPLACEMENT
* OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO IDENTIFIED, OR
* WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT NEEDED TO COMPLY WITH
* ANY SUCH LICENSES OR RIGHTS.
*******************************************************************************/
package edu.ucsb.eucalyptus.admin.client.extensions.store;
import java.util.List;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.DisclosurePanel;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.core.client.GWT;
public class ImageWidget extends Composite {
private static final String DEFAULT_ICON_URI = GraphicsUtil.uri("default-image-icon.png");
private static final String UPGRADE_ICON_URI = GraphicsUtil.uri("available-upgrade.png");
private static final String PROGRESS_BASE_URI = GraphicsUtil.uri("progress-base.png");
private static final String PROGRESS_DONE_URI = GraphicsUtil.uri("progress-done.png");
private static final String INSTALL_BUTTON_URI = GraphicsUtil.uri("install-button.png");
private static final String DOWNLOADING_BUTTON_URI = GraphicsUtil.uri("downloading-button.gif");
private static final String INSTALLING_BUTTON_URI = GraphicsUtil.uri("installing-button.gif");
private static final String INSTALLED_BUTTON_URI = GraphicsUtil.uri("installed-button.png");
private static final String SPINNER_URI = GraphicsUtil.uri("spinner.gif");
/* Left panel. */
private final Image iconImage = new Image();
private final SimplePanel providerTitlePanel = new SimplePanel();
/* Center panel. */
private final Label titleLabel = new Label();
private final Label summaryLabel = new Label();
private final Label readMoreLabel = new Label("read more...");
private final FlowPanel versionPanel = new FlowPanel();
private final Label versionLabel = new InlineLabel("Unknown");
private final FlowPanel sizePanel = new FlowPanel();
private final Label sizeLabel = new InlineLabel("Unknown");
private final FlowPanel tagsPanel = new FlowPanel();
private final Label tagsLabel = new InlineLabel();
private final HTML descriptionHtml = new HTML();
/* Right panel. */
private final FlexTable commandPanel = new FlexTable();
private final SimplePanel buttonPanel = new SimplePanel();
private final Image spinnerImage = new Image(SPINNER_URI);
private final VerticalPanel progressPanel = new VerticalPanel();
private ProgressBarWidget progressBar;
private int howToRunRowIndex;
private int showErrorRowIndex;
private int cancelRowIndex;
private int upgradeIconRowIndex;
private final ImageInfo imageInfo;
private ImageState imageState;
public ImageWidget(ImageInfo imageInfo, ImageState imageState) {
this.imageInfo = imageInfo;
/* Rather than doing this, with a single horizontal panel
*
* [ | | ]
*
* We do this, with two horizontal panels:
*
* [[ | ]| ]
*
* This makes it easier to keep the right-hand side cell
* aligned across different lines, even if the left panels
* shift a little bit. */
HorizontalPanel internalPanel = new HorizontalPanel();
HorizontalPanel externalPanel = new HorizontalPanel();
Widget iconPanel = buildIconPanel();
Widget titlePanel = buildTitlePanel();
Widget commandPanel = buildCommandPanel();
/* Three vertical panels inside a horizontal panel. */
internalPanel.add(iconPanel);
internalPanel.add(titlePanel);
externalPanel.add(internalPanel);
externalPanel.add(commandPanel);
/* For these, the image sizes (icon and button) will dictate
* their real widths. */
internalPanel.setCellWidth(iconPanel, "1px");
externalPanel.setCellWidth(commandPanel, "1px");
internalPanel.setStyleName("istore-left-panels");
initWidget(externalPanel);
setStyleName("istore-image-widget");
setImageTitle(imageInfo.getTitle());
setSummary(imageInfo.getSummary());
setIconUri(imageInfo.getIconUri());
setVersion(imageInfo.getVersion());
setSizeInMB(imageInfo.getSizeInMB());
setProvider(imageInfo.getProviderTitle(), imageInfo.getProviderUri());
setTags(imageInfo.getTags());
setDescriptionHtml(imageInfo.getDescriptionHtml());
setImageState(imageState);
}
public ImageInfo getImageInfo() {
return imageInfo;
}
public ImageState getImageState() {
return imageState;
}
public void setImageState(ImageState imageState) {
this.imageState = imageState;
if (imageState.getStatus() == ImageState.Status.UNKNOWN ||
(imageState.getStatus() == ImageState.Status.UNINSTALLED &&
!imageState.hasAction(ImageState.Action.INSTALL))) {
GWT.log("ERROR: Received unknown image status.", null);
commandPanel.setVisible(false);
} else {
updateButtonImage(imageState.getStatus());
Integer percentage = imageState.getProgressPercentage();
if (percentage == null) {
progressPanel.setVisible(false);
} else {
progressBar.setPercentage(percentage);
progressPanel.setVisible(true);
}
setHowToRunVisible(imageState.getEMI() != null);
setShowErrorVisible(imageState.getErrorMessage() != null);
setCancelVisible(imageState.hasAction(ImageState.Action.CANCEL));
setUpgradeIconVisible(imageState.isUpgrade());
}
}
public void addInstallHandler(InstallHandler<ImageWidget> handler) {
addHandler(handler, InstallEvent.getType());
}
public void addCancelHandler(CancelHandler<ImageWidget> handler) {
addHandler(handler, CancelEvent.getType());
}
public void addClearErrorHandler(ClearErrorHandler<ImageState> handler) {
addHandler(handler, ClearErrorEvent.getType());
}
private Widget buildIconPanel() {
VerticalPanel verticalPanel = new VerticalPanel();
Label byLabel = new Label("by");
verticalPanel.add(iconImage);
verticalPanel.add(byLabel);
verticalPanel.add(providerTitlePanel);
byLabel.setStyleName("istore-provider-title-by");
verticalPanel.setSpacing(3);
verticalPanel.setStyleName("istore-image-icon-panel");
return verticalPanel;
}
private Widget buildTitlePanel() {
FlowPanel visibleDetailsPanel = new FlowPanel();
visibleDetailsPanel.add(versionPanel);
FlowPanel hiddenDetailsPanel = new FlowPanel();
hiddenDetailsPanel.add(sizePanel);
hiddenDetailsPanel.add(tagsPanel);
InlineLabel versionHeaderLabel = new InlineLabel("Image version:");
InlineLabel sizeHeaderLabel = new InlineLabel("Image size:");
InlineLabel tagsHeaderLabel = new InlineLabel("Image tags:");
versionPanel.add(versionHeaderLabel);
versionPanel.add(versionLabel);
sizePanel.add(sizeHeaderLabel);
sizePanel.add(sizeLabel);
tagsPanel.add(tagsHeaderLabel);
tagsPanel.add(tagsLabel);
VerticalPanel readMorePanel = new VerticalPanel();
readMorePanel.add(hiddenDetailsPanel);
readMorePanel.add(descriptionHtml);
DisclosurePanel readMoreDisclosurePanel = new DisclosurePanel();
readMoreDisclosurePanel.setHeader(readMoreLabel);
readMoreDisclosurePanel.setContent(readMorePanel);
VerticalPanel topPanel = new VerticalPanel();
topPanel.add(titleLabel);
topPanel.add(visibleDetailsPanel);
topPanel.add(summaryLabel);
topPanel.add(readMoreDisclosurePanel);
titleLabel.setStyleName("istore-image-title");
summaryLabel.setStyleName("istore-image-summary");
topPanel.setStyleName("istore-title-panel");
readMoreLabel.setStyleName("istore-read-more-label");
visibleDetailsPanel.setStyleName("istore-visible-image-details-panel");
hiddenDetailsPanel.setStyleName("istore-hidden-image-details-panel");
versionHeaderLabel.setStyleName("istore-image-version-label");
versionHeaderLabel.addStyleName("istore-image-detail-label");
versionLabel.setStyleName("istore-image-version-value-label");
versionLabel.addStyleName("istore-image-detail-value-label");
sizeHeaderLabel.setStyleName("istore-image-size-label");
sizeHeaderLabel.addStyleName("istore-image-detail-label");
sizeLabel.setStyleName("istore-image-size-value-label");
sizeLabel.addStyleName("istore-image-detail-value-label");
tagsHeaderLabel.setStyleName("istore-image-tags-label");
tagsHeaderLabel.addStyleName("istore-image-detail-label");
tagsLabel.setStyleName("istore-image-tags-value-label");
tagsLabel.addStyleName("istore-image-detail-value-label");
descriptionHtml.setStyleName("istore-image-description");
ReadMoreOpenCloseHandler rmoch = this.new ReadMoreOpenCloseHandler();
readMoreDisclosurePanel.addOpenHandler(rmoch);
readMoreDisclosurePanel.addCloseHandler(rmoch);
return topPanel;
}
private Widget buildCommandPanel() {
progressBar = new ProgressBarWidget(PROGRESS_BASE_URI,
PROGRESS_DONE_URI,
120, 12);
final Anchor howToRunAnchor = new Anchor("How to run?");
final Anchor showErrorAnchor = new Anchor("Show error");
final Anchor cancelAnchor = new Anchor("Cancel");
Image upgradeIconImage = new Image(UPGRADE_ICON_URI);
commandPanel.setCellSpacing(0);
commandPanel.setCellPadding(0);
spinnerImage.setVisible(false);
commandPanel.setWidget(0, 0, buttonPanel);
commandPanel.setWidget(0, 1, spinnerImage);
commandPanel.setWidget(1, 0, progressPanel);
howToRunRowIndex = 2;
showErrorRowIndex = 3;
cancelRowIndex = 4;
upgradeIconRowIndex = 5;
commandPanel.setWidget(howToRunRowIndex, 0, howToRunAnchor);
commandPanel.setWidget(showErrorRowIndex, 0, showErrorAnchor);
commandPanel.setWidget(cancelRowIndex, 0, cancelAnchor);
commandPanel.setWidget(upgradeIconRowIndex, 0, upgradeIconImage);
progressPanel.add(progressBar);
commandPanel.setStyleName("istore-command-panel");
buttonPanel.setStyleName("istore-button-panel");
spinnerImage.setStyleName("istore-spinner");
progressPanel.setStyleName("istore-progress-panel");
howToRunAnchor.setStyleName("istore-how-to-run-anchor");
showErrorAnchor.setStyleName("istore-show-error-anchor");
cancelAnchor.setStyleName("istore-cancel-anchor");
upgradeIconImage.setStyleName("istore-upgrade-icon");
commandPanel.getRowFormatter().setStyleName(howToRunRowIndex,
"istore-how-to-run-panel");
commandPanel.getRowFormatter().setStyleName(showErrorRowIndex,
"istore-show-error-panel");
commandPanel.getRowFormatter().setStyleName(cancelRowIndex,
"istore-cancel-panel");
commandPanel.getRowFormatter().setStyleName(upgradeIconRowIndex,
"istore-upgrade-icon-panel");
howToRunAnchor.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
event.preventDefault();
ImageInfo imageInfo = ImageWidget.this.imageInfo;
ImageState imageState = ImageWidget.this.imageState;
HowToRunDialog dialog = new HowToRunDialog(imageInfo,
imageState);
dialog.center();
}
});
// Show the error dialog when the anchor is clicked, and hook its
// clear error event into the firing of the same event on this widget.
showErrorAnchor.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
event.preventDefault();
ImageInfo imageInfo = ImageWidget.this.imageInfo;
ImageState imageState = ImageWidget.this.imageState;
ImageErrorDialog dialog = new ImageErrorDialog(imageInfo,
imageState);
dialog.center();
dialog.addClearErrorHandler(new ClearErrorHandler<ImageState>() {
public void onClearError(ClearErrorEvent<ImageState> event) {
ImageWidget.this.fireEvent(event);
}
});
}
});
// Translate the click event into a cancel event.
cancelAnchor.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
setInProgress(true);
ImageWidget.this.fireEvent(new CancelEvent<ImageWidget>(ImageWidget.this));
event.preventDefault();
}
});
return commandPanel;
}
private void setHowToRunVisible(boolean isVisible) {
commandPanel.getRowFormatter().setVisible(howToRunRowIndex, isVisible);
}
private void setShowErrorVisible(boolean isVisible) {
commandPanel.getRowFormatter().setVisible(showErrorRowIndex, isVisible);
}
private void setCancelVisible(boolean isVisible) {
commandPanel.getRowFormatter().setVisible(cancelRowIndex, isVisible);
}
private void setUpgradeIconVisible(boolean isVisible) {
commandPanel.getRowFormatter().setVisible(upgradeIconRowIndex, isVisible);
}
private void setImageTitle(String title) {
titleLabel.setText(title);
}
private void setSummary(String summary) {
summaryLabel.setText(summary);
}
private void setVersion(String version) {
if (version == null) {
versionPanel.setVisible(false);
} else {
versionLabel.setText(version);
versionPanel.setVisible(true);
}
}
private void setSizeInMB(Integer sizeInMB) {
if (sizeInMB == null) {
sizePanel.setVisible(false);
} else {
sizeLabel.setText(sizeInMB.toString() + "MB");
sizePanel.setVisible(true);
}
}
private void setTags(List<String> tags) {
if (tags == null || tags.size() == 0) {
tagsPanel.setVisible(false);
} else {
String joinedTags;
if (tags.size() == 1) {
joinedTags = tags.get(0);
} else {
StringBuilder builder = new StringBuilder();
builder.append(tags.get(0));
for (int i = 1; i != tags.size(); i++) {
builder.append(", ");
builder.append(tags.get(i));
}
joinedTags = builder.toString();
}
tagsLabel.setText(joinedTags);
tagsPanel.setVisible(true);
}
}
private void setDescriptionHtml(String description) {
descriptionHtml.setHTML(description);
}
private void setIconUri(String uri) {
if (uri == null) {
iconImage.setUrl(DEFAULT_ICON_URI);
} else {
iconImage.setUrl(uri);
}
}
private void setProvider(String title, String uri) {
String styleName = "istore-known-provider-title";
if (title == null) {
styleName = "istore-unknown-provider-title";
title = "Unknown";
}
Widget providerTitleWidget;
if (uri == null) {
Label providerLabel = new Label();
providerLabel.setText(title);
providerTitleWidget = providerLabel;
} else {
Anchor providerAnchor = new Anchor();
providerAnchor.setText(title);
providerAnchor.setHref(uri);
providerTitleWidget = providerAnchor;
}
providerTitlePanel.setWidget(providerTitleWidget);
providerTitleWidget.setStyleName(styleName);
providerTitleWidget.addStyleName("istore-provider-title");
}
private Image createButtonImage(ImageState.Status status) {
String uri;
switch (status) {
case UNINSTALLED: uri = INSTALL_BUTTON_URI; break;
case DOWNLOADING: uri = DOWNLOADING_BUTTON_URI; break;
case INSTALLING: uri = INSTALLING_BUTTON_URI; break;
case INSTALLED: uri = INSTALLED_BUTTON_URI; break;
default:
return null; // Unsupported status.
}
return new Image(uri);
}
private void updateButtonImage(ImageState.Status status) {
final Image buttonImage = createButtonImage(status);
// If we don't have support for the given status, prevent any
// commands from happening on this image.
if (buttonImage == null) {
GWT.log("ERROR: Couldn't find an appropriate button for the given status.", null);
commandPanel.setVisible(false);
} else {
commandPanel.setVisible(true);
if (status == ImageState.Status.UNINSTALLED) {
// Translate the click event into a download event.
buttonImage.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
setInProgress(true);
ImageWidget.this.fireEvent(new InstallEvent<ImageWidget>(ImageWidget.this));
}
});
buttonImage.setStyleName("istore-install-button");
}
buttonPanel.setWidget(buttonImage);
}
}
public void setInProgress(boolean inProgress) {
spinnerImage.setVisible(inProgress);
Widget buttonImage = buttonPanel.getWidget();
if (buttonImage != null &&
imageState.getStatus() == ImageState.Status.UNINSTALLED) {
if (inProgress) {
buttonImage.addStyleName("istore-install-button-progress");
} else {
buttonImage.removeStyleName("istore-install-button-progress");
}
}
}
private class ReadMoreOpenCloseHandler
implements OpenHandler<DisclosurePanel>,
CloseHandler<DisclosurePanel> {
public void onOpen(OpenEvent<DisclosurePanel> event) {
readMoreLabel.setText("hide...");
}
public void onClose(CloseEvent<DisclosurePanel> event) {
readMoreLabel.setText("read more...");
}
}
}